/**
  ******************************************************************************
  * @file    bsp_spi.c
  * @author  Iron
  * @date    2018-08-29
  * @version v1.0
  * @brief   bsp_spi c file
  */

/** @addtogroup GROUP_BSP
  * @{
  */

/* includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "semphr.h"
#include "event_groups.h"
#include "stm32f4xx_hal.h"
#include "bsp.h"
#include "bsp_spi.h"

// DEBUG
#include "debug_log.h"
#define LOG_LOCAL_LEVEL DBG_LOG_DEBUG
DBG_LOG_TAG("BSP_SPI");

/* private typedef -----------------------------------------------------------*/
/* private macro -------------------------------------------------------------*/
#define BSP_SPI_DMA_EN
#define BSP_SPI_DMA_MIN_SZIE  5 // CMM SRAM / SPI DMA Conflict resolution

#define BSP_SPI_ENTER_ISR() \
  UBaseType_t uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR()

#define BSP_SPI_EXIT_ISR() \
  portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus)

// #define bsp_spi_os_delay(ms)  vTaskDelay( pdMS_TO_TICKS( ms ) )

#define BSP_SPI1_IRQ_PRE_PRI  7     /* 5 < PRI < 15*/
#define BSP_SPI1_IRQ_SUB_PRI  0

/**
  * @breaf SPI1 RTOS DEFINE
    */
#define BSP_SPI_OS_TRUE   pdTRUE
#define BSP_SPI_OS_FALSE  pdFALSE

/**
  * @breaf SPI1 evetns handle
    */
#define BSP_SPI1_ERR_EVT    (1 << 0)
#define BSP_SPI1_RXC_EVT    (1 << 1)
#define BSP_SPI1_TXC_EVT    (1 << 2)

#define BSP_SPI1_OS_EVENT_INIT() \
    do{spi1_event_handle = xEventGroupCreateStatic( &spi1_event );}while(0)

#define BSP_SPI1_OS_EVENT_CLEAR(events) \
    do{xEventGroupClearBits(spi1_event_handle, events);}while(0)

#define BSP_SPI1_OS_EVENT_SET_FROM_ISR(events) \
    do{                                                                                                                                                                                                \
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;                                                     \
    if (xEventGroupSetBitsFromISR( spi1_event_handle, events, &xHigherPriorityTaskWoken ) != pdFAIL) { \
      portYIELD_FROM_ISR( xHigherPriorityTaskWoken );                                                  \
    }                                                                                                                                                                                            \
    }while(0)

#define BSP_SPI1_OS_EVENT_WAIT(events, is_clear, wait_all, timeout) \
    xEventGroupWaitBits( spi1_event_handle, events, is_clear, wait_all, pdMS_TO_TICKS(timeout));

static EventGroupHandle_t spi1_event_handle;
static StaticEventGroup_t spi1_event;

/**
  * @breaf spi1 mutex handle
  */
static SemaphoreHandle_t bsp_spi1_mutex_handle;
static StaticSemaphore_t bsp_spi1_mutex;

#define BSP_SPI1_OS_MUTEX_INIT() \
    do{bsp_spi1_mutex_handle = xSemaphoreCreateMutexStatic(&bsp_spi1_mutex);}while(0)

#define BSP_SPI1_OS_MUTEX_WAIT(timeout) \
    do{                                                                                                                                                              \
        if( xSemaphoreTake(bsp_spi1_mutex_handle, pdMS_TO_TICKS(timeout)) != pdPASS) {   \
            DBG_LOGE(TAG, "spi1 mutex wait error or timeout.");                            \
            return BSP_SPI_TIMEOUT;                                                                                                              \
        }                                                                                \
    }while(0)

#define BSP_SPI1_OS_MUTEX_RELEASE() \
    do{xSemaphoreGive(bsp_spi1_mutex_handle);}while(0)

/* private variables ---------------------------------------------------------*/
SPI_HandleTypeDef hspi1;
DMA_HandleTypeDef hdma_spi1_rx;
DMA_HandleTypeDef hdma_spi1_tx;

/* private function prototypes -----------------------------------------------*/
/* private functions ---------------------------------------------------------*/
extern void _Error_Handler(char *, int);

/**
* @brief This function handles SPI1 global interrupt.
*/
void SPI1_IRQHandler(void)
{
    BSP_SPI_ENTER_ISR();
    HAL_SPI_IRQHandler(&hspi1);
    BSP_SPI_EXIT_ISR();
}


#ifdef BSP_SPI_DMA_EN
/**
* @brief This function handles DMA2 stream0 global interrupt.
*/
void DMA2_Stream2_IRQHandler(void)
{
    BSP_SPI_ENTER_ISR();
    HAL_DMA_IRQHandler(&hdma_spi1_rx);
    BSP_SPI_EXIT_ISR();
}

/**
* @brief This function handles DMA2 stream3 global interrupt.
*/
void DMA2_Stream3_IRQHandler(void)
{
    BSP_SPI_ENTER_ISR();
    HAL_DMA_IRQHandler(&hdma_spi1_tx);
    BSP_SPI_EXIT_ISR();
}

#endif

void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    if (hspi->Instance == SPI1)
    {
        /* Peripheral clock enable */
        __HAL_RCC_GPIOA_CLK_ENABLE();
        __HAL_RCC_SPI1_CLK_ENABLE();

        /**SPI1 GPIO Configuration
        PA5     ------> SPI1_SCK
        PA6     ------> SPI1_MISO
        PA7     ------> SPI1_MOSI
        */
        GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

#ifdef BSP_SPI_DMA_EN
        /* SPI1 DMA Init */
        /* SPI1_RX Init */
        hdma_spi1_rx.Instance = DMA2_Stream2;
        hdma_spi1_rx.Init.Channel = DMA_CHANNEL_3;
        hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
        hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
        hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE;
        hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
        hdma_spi1_rx.Init.Mode = DMA_NORMAL;
        hdma_spi1_rx.Init.Priority = DMA_PRIORITY_LOW;
        hdma_spi1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
        if (HAL_DMA_Init(&hdma_spi1_rx) != HAL_OK)
        {
            _Error_Handler(__FILE__, __LINE__);
        }

        __HAL_LINKDMA(hspi, hdmarx, hdma_spi1_rx);

        /* SPI1_TX Init */
        hdma_spi1_tx.Instance = DMA2_Stream3;
        hdma_spi1_tx.Init.Channel = DMA_CHANNEL_3;
        hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
        hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
        hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;
        hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
        hdma_spi1_tx.Init.Mode = DMA_NORMAL;
        hdma_spi1_tx.Init.Priority = DMA_PRIORITY_LOW;
        hdma_spi1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
        if (HAL_DMA_Init(&hdma_spi1_tx) != HAL_OK)
        {
            _Error_Handler(__FILE__, __LINE__);
        }

        __HAL_LINKDMA(hspi, hdmatx, hdma_spi1_tx);
#endif

        /* SPI1 interrupt Init */
        HAL_NVIC_SetPriority(SPI1_IRQn, BSP_SPI1_IRQ_PRE_PRI, BSP_SPI1_IRQ_SUB_PRI);
        HAL_NVIC_EnableIRQ(SPI1_IRQn);
    }
}

void HAL_SPI_MspDeInit(SPI_HandleTypeDef *hspi)
{
    if (hspi->Instance == SPI1)
    {
        /* Peripheral clock disable */
        __HAL_RCC_SPI1_CLK_DISABLE();

        /**SPI1 GPIO Configuration
        PA5     ------> SPI1_SCK
        PA6     ------> SPI1_MISO
        PA7     ------> SPI1_MOSI
        */
        HAL_GPIO_DeInit(GPIOA, GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7);

#ifdef BSP_SPI_DMA_EN
        /* SPI1 DMA DeInit */
        HAL_DMA_DeInit(hspi->hdmarx);
        HAL_DMA_DeInit(hspi->hdmatx);
#endif
        /* SPI1 interrupt DeInit */
        HAL_NVIC_DisableIRQ(SPI1_IRQn);
    }
}

/* SPI1 init function */
static void MX_SPI1_Init(void)
{
    /* SPI1 parameter configuration*/
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_MASTER;
    hspi1.Init.Direction = SPI_DIRECTION_2LINES;
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspi1.Init.NSS = SPI_NSS_SOFT;
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    hspi1.Init.CRCPolynomial = 10;
    if (HAL_SPI_Init(&hspi1) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }
}

/**
  * Enable DMA controller clock
  */
#ifdef BSP_SPI_DMA_EN
static void MX_DMA_Init(void)
{
    /* DMA controller clock enable */
    __HAL_RCC_DMA2_CLK_ENABLE();

    /* DMA interrupt init */
    /* DMA2_Stream0_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, BSP_SPI1_IRQ_PRE_PRI, BSP_SPI1_IRQ_SUB_PRI);
    HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);
    /* DMA2_Stream3_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, BSP_SPI1_IRQ_PRE_PRI, BSP_SPI1_IRQ_SUB_PRI);
    HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);

}
#endif

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
    if (hspi->Instance == SPI1)
    {
        BSP_SPI1_OS_EVENT_SET_FROM_ISR(BSP_SPI1_TXC_EVT);
    }
}

void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
    if (hspi->Instance == SPI1)
    {
        BSP_SPI1_OS_EVENT_SET_FROM_ISR(BSP_SPI1_RXC_EVT);
    }
}

void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
    if (hspi->Instance == SPI1)
    {
        BSP_SPI1_OS_EVENT_SET_FROM_ISR(BSP_SPI1_ERR_EVT);
    }
}

int32_t bsp_spi_mutex_wait(uint32_t timeout)
{
    BSP_SPI1_OS_MUTEX_WAIT(timeout);
    return BSP_SPI_NO_ERR;
}

int32_t bsp_spi_mutex_release(void)
{
    BSP_SPI1_OS_MUTEX_RELEASE();
    return BSP_SPI_NO_ERR;
}

static int32_t bsp_spi_wait_ready(uint32_t timeout)
{
    uint32_t tms;

    tms = bsp_get_time_ms();

    while (HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY)
    {
        if ((bsp_get_time_ms() - tms) > timeout)
        {
            return BSP_SPI_TIMEOUT;
        }
        DBG_LOGW(TAG, "SPI1 wait ready... ");
        bsp_os_delay(1);
    }

    return BSP_SPI_NO_ERR;
}

int32_t bsp_spi_write(uint8_t *data, uint16_t len, uint32_t timeout)
{
    uint32_t evt_bits;

    if (bsp_spi_wait_ready(timeout) != BSP_SPI_NO_ERR)
    {
        DBG_LOGW(TAG, "SPI1 wait ready error.");
        return BSP_SPI_TIMEOUT;
    }

    if (len <= BSP_SPI_DMA_MIN_SZIE)
    {
        return (HAL_SPI_Transmit(&hspi1, data, len, timeout) == HAL_OK) ? BSP_SPI_NO_ERR : BSP_SPI_ERR;
    }

    BSP_SPI1_OS_EVENT_CLEAR(BSP_SPI1_TXC_EVT | BSP_SPI1_ERR_EVT);

#ifdef BSP_SPI_DMA_EN
    if (HAL_SPI_Transmit_DMA(&hspi1, data, len) != HAL_OK)
    {
#else
    if (HAL_SPI_Transmit_IT(&hspi1, data, len) != HAL_OK)
    {
#endif
        DBG_LOGE(TAG, "spi1 transmit error. (State:%d, ErrorCode=%d, len=%d)", hspi1.State, hspi1.ErrorCode, len);
        return BSP_SPI_ERR;
    }

    evt_bits = BSP_SPI1_OS_EVENT_WAIT(BSP_SPI1_TXC_EVT | BSP_SPI1_ERR_EVT, BSP_SPI_OS_TRUE, BSP_SPI_OS_FALSE, timeout);

    if (evt_bits & BSP_SPI1_ERR_EVT)
    {
        DBG_LOGE(TAG, "spi1 read error. (State:%d, ErrorCode=%d)", hspi1.State, hspi1.ErrorCode);
    }

    return (evt_bits & BSP_SPI1_TXC_EVT) ? BSP_SPI_NO_ERR : BSP_SPI_TIMEOUT;
}

int32_t bsp_spi_read(uint8_t *data, uint16_t len, uint32_t timeout)
{
    uint32_t evt_bits;

    if (bsp_spi_wait_ready(timeout) != BSP_SPI_NO_ERR)
    {
        DBG_LOGW(TAG, "SPI1 wait ready error.");
        return BSP_SPI_TIMEOUT;
    }

    if (len <= BSP_SPI_DMA_MIN_SZIE)
    {
        return (HAL_SPI_Receive(&hspi1, data, len, timeout) == HAL_OK) ? BSP_SPI_NO_ERR : BSP_SPI_ERR;
    }

    BSP_SPI1_OS_EVENT_CLEAR(BSP_SPI1_RXC_EVT | BSP_SPI1_ERR_EVT);

#ifdef BSP_SPI_DMA_EN
    if (HAL_SPI_Receive_DMA(&hspi1, data, len) != HAL_OK)
    {
#else
    if (HAL_SPI_Receive_IT(&hspi1, data, len) != HAL_OK)
    {
#endif
        DBG_LOGE(TAG, "spi1 receive error. (State:%d, ErrorCode=%d, len=%d)", hspi1.State, hspi1.ErrorCode, len);
        return BSP_SPI_ERR;
    }

    evt_bits = BSP_SPI1_OS_EVENT_WAIT(BSP_SPI1_RXC_EVT | BSP_SPI1_ERR_EVT, BSP_SPI_OS_TRUE, BSP_SPI_OS_FALSE, timeout);

    if (evt_bits & BSP_SPI1_ERR_EVT)
    {
        DBG_LOGE(TAG, "spi1 read error. (State:%d, ErrorCode=%d)", hspi1.State, hspi1.ErrorCode);
    }

    return (evt_bits & BSP_SPI1_RXC_EVT) ? BSP_SPI_NO_ERR : BSP_SPI_TIMEOUT;
}

int32_t bsp_spi_init(void)
{
    BSP_SPI1_OS_MUTEX_INIT();
    BSP_SPI1_OS_EVENT_INIT();

    MX_SPI1_Init();

#ifdef BSP_SPI_DMA_EN
    MX_DMA_Init();
#endif

    return 0;
}

/**
  * @}
  */

/******************* (C)COPYRIGHT 2018 ***** END OF FILE *********************/
